package com.doeis.core.db;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

import javax.sql.DataSource;

import org.apache.log4j.Logger;

import com.doeis.core.db.transaction.Transaction;
import com.doeis.core.db.transaction.TransactionSynchronizer;
import com.doeis.tools.BeanUtils;

public class DBManager {
	private static final Logger log = Logger.getLogger(DBManager.class);
	private static Map<String, DataSource> dataSources = new HashMap<String, DataSource>();
	private static boolean show_sql = false;
	public static String defDataSourceName = null;
	private static boolean rwSeparate = false;
	private static String readDataSourceName = null;

	static {
		initDataSource(null);
	}

	private static final void initDataSource(Properties dbProperties) {
		try {
			if (dbProperties == null) {
				dbProperties = new Properties();
				dbProperties.load(DBManager.class.getResourceAsStream("/system.properties"));
			}

			defDataSourceName = dbProperties.getProperty("datasource.default", "plat");
			show_sql = Boolean.valueOf(dbProperties.getProperty("datasource.show_sql", "false")).booleanValue();

			rwSeparate = Boolean.valueOf(dbProperties.getProperty("datasource.default.rw.separate", "false")).booleanValue();
			if (rwSeparate) {
				readDataSourceName = dbProperties.getProperty("datasource.read");
			}

			Map<String, Properties> prosMap = new HashMap<String, Properties>();
			Properties cp_props = null;
			String[] jdbcFields;
			for (Iterator<Object> localIterator = dbProperties.keySet().iterator(); localIterator.hasNext();) {
				Object key = localIterator.next();
				String skey = (String) key;
				jdbcFields = null;
				if (skey.startsWith("jdbc.")) {
					jdbcFields = skey.split("\\.");
					if ((jdbcFields != null) && (jdbcFields.length != 3))
						continue;
					String dataSourceName = jdbcFields[1];
					String name = jdbcFields[2];

					cp_props = (Properties) prosMap.get(dataSourceName);
					if (cp_props == null) {
						cp_props = new Properties();
						prosMap.put(dataSourceName, cp_props);
					}
					cp_props.put(name, dbProperties.getProperty(skey));
				}
			}

			Set<String> dataSourceNames = prosMap.keySet();
			DataSource dataSource = null;
			for (String dataSourceName : dataSourceNames)
				try {
					cp_props = (Properties) prosMap.get(dataSourceName);
					if (dataSourceName.equals(defDataSourceName)) {
						log.info("Default DataSource:" + defDataSourceName);
					}
					dataSource = (DataSource) Class.forName(cp_props.getProperty("datasource")).newInstance();
					if (dataSource.getClass().getName().indexOf("c3p0") > 0) {
						System.setProperty("com.mchange.v2.c3p0.management.ManagementCoordinator",
								"com.mchange.v2.c3p0.management.NullManagementCoordinator");
					}
					log.info("Test DataSource : " + dataSourceName + "--" + dataSource.getClass().getName());
					BeanUtils.populate(dataSource, cp_props);
					dataSources.put(dataSourceName, dataSource);
					TransactionSynchronizer.initDataSource(dataSourceName);

					Connection conn = getConnection(dataSourceName);
					DatabaseMetaData mdm = conn.getMetaData();
					log.info("Connected to " + mdm.getDatabaseProductName() + " " + mdm.getDatabaseProductVersion());
					closeBindConnection(dataSourceName);
				} catch (Exception e) {
					log.warn("数据源'" + dataSourceName + "'初始化失败", e);
				}
		} catch (Exception e) {
			log.error(e.getMessage(), e);
		}
	}

	public static final Set<String> dataSourceNames() {
		return dataSources.keySet();
	}

	public static final String getDefaultDataSourceName(boolean isReadDataSource) {
		if ((rwSeparate) && (isReadDataSource)) {
			return readDataSourceName;
		}
		return defDataSourceName;
	}

	public static boolean isRwSeparate() {
		return rwSeparate;
	}

	public static String getReadDataSourceName() {
		return readDataSourceName;
	}

	public static Map<String, DataSource> getDataSources() {
		return dataSources;
	}

	public static final void closeDataSource(String dataSourceName) {
		try {
			DataSource dataSource = (DataSource) dataSources.get(dataSourceName);
			if (dataSource != null)
				dataSource.getClass().getMethod("close", new Class[0]).invoke(dataSource, new Object[0]);
		} catch (NoSuchMethodException localNoSuchMethodException) {
		} catch (Exception e) {
			log.error("Unabled to destroy DataSource!!! ", e);
		}
	}

	public static final Connection getConnection() throws SQLException {
		return getConnection(defDataSourceName);
	}

	public static final Connection getConnection(String dataSourceName) throws SQLException {
		Connection conn = null;

		Transaction transaction = TransactionSynchronizer.getLastTransaction(dataSourceName);
		if (transaction != null) {
			conn = transaction.getConn();
			if ((conn == null) || (conn.isClosed())) {
				conn = getConnection(dataSourceName, transaction.getPropagation() != 2);
			}
		} else {
			conn = getConnection(dataSourceName, true);
		}
		log.debug("get conn:" + dataSourceName + "-" + (conn == null ? "null" : Integer.valueOf(conn.hashCode())));
		return (show_sql) && (!Proxy.isProxyClass(conn.getClass())) ? new _DebugConnection(conn).getConnection() : conn;
	}

	private static Connection getConnection(String dataSourceName, boolean needBind) throws SQLException {
		try {
			Connection conn = null;
			if (needBind) {
				conn = TransactionSynchronizer.getConnectionResource(dataSourceName);
				if ((conn == null) || (conn.isClosed())) {
					DataSource dataSource = (DataSource) dataSources.get(dataSourceName);
					conn = dataSource.getConnection();
					TransactionSynchronizer.bindConnectionResource(dataSourceName, conn);
				}
			} else {
				DataSource dataSource = (DataSource) dataSources.get(dataSourceName);
				conn = dataSource.getConnection();
			}
			return conn;
		} catch (SQLException e) {
		}
		throw new SQLException("Failed to get connection from :" + dataSourceName);
	}

	public static void closeAllBindConnection() {
		Set<String> dataSourceNames = dataSourceNames();
		for (String dataSourceName : dataSourceNames)
			closeBindConnection(dataSourceName);
	}

	public static void closeBindConnection(String dataSourceName) {
		Connection conn = TransactionSynchronizer.getConnectionResource(dataSourceName);
		if (conn != null) {
			TransactionSynchronizer.unbindConnectionResource(dataSourceName);
			closeConnection(conn);
		}
	}

	public static void closeConnection(Connection conn) {
		if (conn != null)
			try {
				log.debug("close conn:" + conn.hashCode());
				if (!conn.isClosed()) {
					conn.setAutoCommit(true);
					conn.close();
					conn = null;
				}
			} catch (SQLException e) {
				log.error("Unabled to close connection!!! ", e);
			}
	}

	public static void main(String[] args) {
	}

	static class _DebugConnection implements InvocationHandler {
		private static final Logger log = Logger.getLogger(_DebugConnection.class);

		private Connection conn = null;

		public _DebugConnection(Connection conn) {
			this.conn = conn;
		}

		public Connection getConnection() {
			return (Connection) Proxy.newProxyInstance(this.conn.getClass().getClassLoader(), this.conn.getClass().getInterfaces(), this);
		}

		public Object invoke(Object proxy, Method m, Object[] args) throws Throwable {
			try {
				String method = m.getName();
				if (("prepareStatement".equals(method)) || ("createStatement".equals(method))) {
					log.info("[SQL] >>> " + args[0]);
				}
				return m.invoke(this.conn, args);
			} catch (InvocationTargetException e) {
				throw e.getTargetException();
			}
		}
	}
}